package com.platform.boot.security;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.platform.boot.security.group.member.GroupMemberOnly;
import com.platform.boot.security.tenant.member.TenantMember;
import com.platform.boot.security.tenant.member.TenantMemberOnly;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import java.io.Serializable;
import java.util.*;

/**
 * @author Alex bob(<a href="https://github.com/vnobo">https://github.com/vnobo</a>)
 */
@Data
public final class SecurityUserDetails implements UserDetails {

    private String userCode;
    private String username;
    private String password;
    private Boolean enabled;
    private Set<GrantedAuthority> authorities;
    private Set<TenantMemberOnly> tenants;
    private Set<GroupMemberOnly> groups;

    public static SecurityUserDetails of(String code, String username, String password, Boolean enabled) {
        Assert.isTrue(StringUtils.hasLength(code) & StringUtils.hasLength(username)
                        && StringUtils.hasLength(password),
                "Cannot pass null or empty values to constructor");
        SecurityUserDetails securityDetails = new SecurityUserDetails();
        securityDetails.setUserCode(code);
        securityDetails.setUsername(username);
        securityDetails.setPassword(password);
        securityDetails.setEnabled(enabled);
        return securityDetails;
    }

    private static SortedSet<GrantedAuthority> sortAuthorities(Collection<? extends GrantedAuthority> authorities) {
        Assert.notNull(authorities, "Cannot pass a null GrantedAuthority collection");
        // Ensure array iteration order is predictable (as per
        // UserDetails.getAuthorities() contract and SEC-717)
        SortedSet<GrantedAuthority> sortedAuthorities = new TreeSet<>(new AuthorityComparator());
        for (GrantedAuthority grantedAuthority : authorities) {
            Assert.notNull(grantedAuthority, "GrantedAuthority list cannot contain any null elements");
            sortedAuthorities.add(grantedAuthority);
        }
        return sortedAuthorities;
    }

    public SecurityUserDetails groups(Set<GroupMemberOnly> groups) {
        this.setGroups(groups);
        return this;
    }

    public SecurityUserDetails tenants(Set<TenantMemberOnly> tenants) {
        this.setTenants(tenants);
        return this;
    }

    public String getTenantCode() {
        return tenants.parallelStream().filter(TenantMember::getEnabled).findAny()
                .orElse(new TenantMemberOnly()).getTenantCode();
    }

    @Override
    public boolean isAccountNonExpired() {
        return this.enabled;
    }

    @Override
    public boolean isAccountNonLocked() {
        return this.enabled;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return this.enabled;
    }

    @Override
    public boolean isEnabled() {
        return this.enabled;
    }

    public UserDetails authorities(Set<GrantedAuthority> authorities) {
        this.setAuthorities(Collections.unmodifiableSet(sortAuthorities(authorities)));
        return this;
    }

    @Override
    @JsonIgnore
    public String getPassword() {
        return this.password;
    }

    private static class AuthorityComparator implements Comparator<GrantedAuthority>, Serializable {
        @Override
        public int compare(GrantedAuthority g1, GrantedAuthority g2) {
            // Neither should ever be null as each entry is checked before adding it to
            // the set. If the authority is null, it is a custom authority and should
            // precede others.
            if (g2.getAuthority() == null) {
                return -1;
            }
            if (g1.getAuthority() == null) {
                return 1;
            }
            return g1.getAuthority().compareTo(g2.getAuthority());
        }

    }
}